package com.quick.proxy.core.utils;

import com.quick.proxy.core.ChannelAttribute;
import com.quick.proxy.core.ChannelConstants;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.concurrent.GenericFutureListener;
import lombok.extern.slf4j.Slf4j;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.util.*;

/**
 * Description: 代理工具类
 * Author: 吃素不吃肉
 * Date: 2023/7/4
 *
 * @author 吃素不吃肉
 */
@Slf4j
public class ProxyUtils {
    private static final ServerBootstrap VISITOR_PROXY_SERVER_BOOTSTRAP = new ServerBootstrap();
    private static final EventLoopGroup VISITOR_PROXY_SERVER_BOSS_GROUP = new NioEventLoopGroup();
    private static final EventLoopGroup VISITOR_PROXY_SERVER_WORK_GROUP = new NioEventLoopGroup();
    private static final ServerBootstrap PROXY_SERVER_BOOTSTRAP = new ServerBootstrap();
    private static final EventLoopGroup PROXY_SERVER_BOSS_GROUP = new NioEventLoopGroup();
    private static final EventLoopGroup PROXY_SERVER_WORK_GROUP = new NioEventLoopGroup();
    private static final Bootstrap PROXY_CLIENT_BOOTSTRAP = new Bootstrap();
    private static final EventLoopGroup PROXY_CLIENT_GROUP = new NioEventLoopGroup();
    private static final Bootstrap REAL_PROXY_CLIENT_BOOTSTRAP = new Bootstrap();
    private static final EventLoopGroup REAL_PROXY_CLIENT_GROUP = new NioEventLoopGroup();

    /**
     * 获取客户端远程地址
     *
     * @param channel 通道
     * @return 地址
     */
    public static String getRemoteAddress(Channel channel) {
        InetSocketAddress remoteAddress = (InetSocketAddress) channel.remoteAddress();
        String clientIp = remoteAddress.getAddress().getHostAddress();
        int port = remoteAddress.getPort();
        return clientIp + ":" + port;
    }

    /**
     * 获取本地端口号
     *
     * @param channel 通道
     * @return 端口号
     */
    public static int getLocalPort(Channel channel) {
        SocketAddress localAddress = channel.localAddress();
        if (localAddress instanceof InetSocketAddress) {
            InetSocketAddress inetSocketAddress = (InetSocketAddress) localAddress;
            return inetSocketAddress.getPort();
        }
        return 0;
    }

    /**
     * 获取本地ip地址
     *
     * @return 本地ip地址
     */
    public static String getLocalAddress() {
        try {
            InetAddress localHost = InetAddress.getLocalHost();
            return localHost.getHostAddress();
        } catch (UnknownHostException ignored) {
        }
        return "127.0.0.1";
    }

    /**
     * 创建访问者服务端
     *
     * @param channel                代理通道
     * @param realProxyClientAddress 真实代理客户端地址
     * @param port                   访问者服务端端口
     * @param channelInitializer     用户客户端与访问者服务端之间的处理器链路
     */
    public static void createVisitorProxyServer(Channel channel, String realProxyClientAddress, int port, ChannelInitializer<SocketChannel> channelInitializer) {
        try {
            synchronized (VISITOR_PROXY_SERVER_BOOTSTRAP) {
                if (VISITOR_PROXY_SERVER_BOOTSTRAP.config().group() == null) {
                    configureVisitorProxyServerBootstrap(channelInitializer);
                }
            }
            ChannelFuture channelFuture = VISITOR_PROXY_SERVER_BOOTSTRAP.bind(port).sync();
            channelFuture.addListener((GenericFutureListener<ChannelFuture>) future -> {
                if (future.isSuccess()) {
                    String accessKey = channel.attr(ChannelAttribute.ACCESS_KEY_PROXY_CHANNEL).get();
                    log.info("[{}]Successfully started visitor proxy server port {}", accessKey, port);
                    //代理通道绑定访问者通道
                    synchronized (VISITOR_PROXY_SERVER_BOOTSTRAP) {
                        List<Channel> visitorChannels = Optional.ofNullable(channel.attr(ChannelAttribute.VISITOR_CHANNEL).get()).orElse(new ArrayList<>());
                        visitorChannels.add(future.channel());
                        channel.attr(ChannelAttribute.VISITOR_CHANNEL).set(visitorChannels);
                    }
                    //映射端口绑定代理通道
                    ChannelConstants.MAPPING_PORT_PROXY_CHANNEL.put(port, channel);
                    //映射端口绑定真实代理客户端地址
                    ChannelConstants.MAPPING_PORT_REAL_PROXY_CLIENT_IP_PORT.put(port, realProxyClientAddress);
                } else {
                    log.warn("Visitor proxy server port {} failed to start", port);
                }
            });

            channelFuture.channel().closeFuture().sync();
            VISITOR_PROXY_SERVER_BOSS_GROUP.terminationFuture().sync();
            VISITOR_PROXY_SERVER_WORK_GROUP.terminationFuture().sync();
            log.info("Visitor proxy server {} close success", port);
        } catch (InterruptedException ignored) {
        } finally {
            VISITOR_PROXY_SERVER_BOSS_GROUP.shutdownGracefully();
            VISITOR_PROXY_SERVER_WORK_GROUP.shutdownGracefully();
        }
    }

    /**
     * 创建代理服务端
     *
     * @param port               代理服务端端口
     * @param channelInitializer 代理客户端与代理服务端之间的处理器链路
     */
    public static void createProxyServer(int port, ChannelInitializer<SocketChannel> channelInitializer) {
        try {
            synchronized (PROXY_SERVER_BOOTSTRAP) {
                if (PROXY_SERVER_BOOTSTRAP.config().group() == null) {
                    configureProxyServerBootstrap(channelInitializer);
                }
            }
            ChannelFuture channelFuture = PROXY_SERVER_BOOTSTRAP.bind(port).sync();
            channelFuture.addListener((GenericFutureListener<ChannelFuture>) future -> {
                if (future.isSuccess()) {
                    log.info("Successfully started proxy server port {}", port);
                } else {
                    log.warn("Proxy server port {} failed to start", port);
                }
            });
            channelFuture.channel().closeFuture().sync();
            PROXY_SERVER_BOSS_GROUP.terminationFuture().sync();
            PROXY_SERVER_WORK_GROUP.terminationFuture().sync();
        } catch (InterruptedException ignored) {
        } finally {
            PROXY_SERVER_BOSS_GROUP.shutdownGracefully();
            PROXY_SERVER_WORK_GROUP.shutdownGracefully();
        }
    }

    /**
     * 创建代理客户端,用于连接代理服务端
     *
     * @param ip                 代理服务端ip
     * @param port               代理服务端端口
     * @param channelInitializer 代理客户端与代理服务端之间的处理器链路
     */
    public static void createProxyClient(String ip, int port, ChannelInitializer<SocketChannel> channelInitializer) {
        try {
            synchronized (PROXY_CLIENT_BOOTSTRAP) {
                if (PROXY_CLIENT_BOOTSTRAP.config().group() == null) {
                    configureProxyClientBootstrap(channelInitializer);
                }
            }
            ChannelFuture channelFuture = PROXY_CLIENT_BOOTSTRAP.connect(ip, port);
            channelFuture.addListener((GenericFutureListener<ChannelFuture>) future -> {
                if (future.isSuccess()) {
                    log.info("Proxy client successfully connected to the proxy server {}:{}", ip, port);
                } else {
                    log.warn("Proxy client failed to connect to proxy server {}:{}", ip, port);
                    //将失败,无效的连接,通过处理器链路传递到各个handler中
                    future.channel().pipeline().fireChannelInactive();
                }
            });
            channelFuture.channel().closeFuture().sync();
            PROXY_CLIENT_GROUP.terminationFuture().sync();
            log.info("Successfully closed proxy client port {}", port);
        } catch (InterruptedException ignored) {
        } finally {
            PROXY_CLIENT_GROUP.shutdownGracefully();
        }
    }

    /**
     * 创建真实代理客户端,用于连接被代理客户端
     *
     * @param ip                 被代理客户端ip
     * @param port               被代理客户端端口
     * @param visitorId          访问者id
     * @param channelInitializer 真实代理客户端与被代理客户端之间的处理器链路
     */
    public static void createRealProxyClient(String ip, int port, String visitorId, ChannelInitializer<SocketChannel> channelInitializer) {
        try {
            synchronized (REAL_PROXY_CLIENT_BOOTSTRAP) {
                if (REAL_PROXY_CLIENT_BOOTSTRAP.config().group() == null) {
                    configureRealProxyClientBootstrap(channelInitializer);
                }
            }
            ChannelFuture channelFuture = REAL_PROXY_CLIENT_BOOTSTRAP.connect(ip, port);
            channelFuture.addListener((GenericFutureListener<ChannelFuture>) future -> {
                if (future.isSuccess()) {
                    log.info("visitorId[{}]Proxy client successfully connected to the real proxy client {}:{}", visitorId, ip, port);
                    //访问者id绑定真实代理通道
                    future.channel().attr(ChannelAttribute.VISITOR_ID).set(visitorId);
                    //将访问者id对应的真实代理通道绑定到代理通道上
                    Channel channel = ChannelConstants.VISITOR_ID_PROXY_CHANNEL.get(visitorId);
                    synchronized (REAL_PROXY_CLIENT_BOOTSTRAP) {
                        if (channel != null) {
                            Map<String, Channel> visitorIdRealProxyChannel = Optional.ofNullable(channel.attr(ChannelAttribute.VISITOR_ID_REAL_PROXY_CHANNEL).get()).orElse(new HashMap<>(1));
                            visitorIdRealProxyChannel.put(visitorId, future.channel());
                            channel.attr(ChannelAttribute.VISITOR_ID_REAL_PROXY_CHANNEL).set(visitorIdRealProxyChannel);
                            log.info("visitorId[{}]真实代理通道绑定到代理通道成功", visitorId);
                            //释放锁,此时当前visitorId可以读取代理数据
                            LockUtils.VisitorLock visitorLock = LockUtils.VISITOR_LOCK.get(visitorId);
                            visitorLock.signal();
                        }
                    }
                } else {
                    log.warn("visitorId[{}]Proxy client failed to connect to real proxy client {}:{}", visitorId, ip, port);
                    //将失败,无效的连接,通过处理器链路传递到各个handler中
                    future.channel().pipeline().fireChannelInactive();
                }
            });
            channelFuture.channel().closeFuture().sync();
            REAL_PROXY_CLIENT_GROUP.terminationFuture().sync();
        } catch (InterruptedException ignored) {
        } finally {
            REAL_PROXY_CLIENT_GROUP.shutdownGracefully();
        }
    }

    private static void configureProxyClientBootstrap(ChannelInitializer<SocketChannel> channelInitializer) {
        PROXY_CLIENT_BOOTSTRAP.group(PROXY_CLIENT_GROUP)
                .channel(NioSocketChannel.class)
                .handler(channelInitializer);
    }

    private static void configureRealProxyClientBootstrap(ChannelInitializer<SocketChannel> channelInitializer) {
        REAL_PROXY_CLIENT_BOOTSTRAP.group(REAL_PROXY_CLIENT_GROUP)
                .channel(NioSocketChannel.class)
                .handler(channelInitializer);
    }

    private static void configureProxyServerBootstrap(ChannelInitializer<SocketChannel> channelInitializer) {
        PROXY_SERVER_BOOTSTRAP.group(PROXY_SERVER_BOSS_GROUP, PROXY_SERVER_WORK_GROUP)
                .channel(NioServerSocketChannel.class)
                .childHandler(channelInitializer)
                .option(ChannelOption.SO_BACKLOG, 128)
                .childOption(ChannelOption.TCP_NODELAY, true)
                .childOption(ChannelOption.SO_KEEPALIVE, true);
    }

    private static void configureVisitorProxyServerBootstrap(ChannelInitializer<SocketChannel> channelInitializer) {
        VISITOR_PROXY_SERVER_BOOTSTRAP.group(VISITOR_PROXY_SERVER_BOSS_GROUP, VISITOR_PROXY_SERVER_WORK_GROUP)
                .channel(NioServerSocketChannel.class)
                .childHandler(channelInitializer)
                .option(ChannelOption.SO_BACKLOG, 128)
                .childOption(ChannelOption.TCP_NODELAY, true)
                .childOption(ChannelOption.SO_KEEPALIVE, true);
    }

    public static class Address {
        private final String[] address;

        Address(String[] address) {
            this.address = address;
        }

        public static Address create(String[] address) {
            return new Address(address);
        }

        public String getIp() {
            return address[0];
        }

        public int getPort() {
            return Integer.parseInt(address[1]);
        }
    }

}
